balance = 0;

// the extension's id from install.rdf
var FOXTIDY_ID = "foxtidy@shining365";
var em = Components.classes["@mozilla.org/extensions/manager;1"].
         getService(Components.interfaces.nsIExtensionManager);
// the path may use forward slash ("/") as the delimiter
// returns nsIFile for the extension's install.rdf
var file = em.getInstallLocation(FOXTIDY_ID).getItemFile(FOXTIDY_ID, "foxtidy.log");

// file is nsIFile, data is a string
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                         .createInstance(Components.interfaces.nsIFileOutputStream);
// use 0x02 | 0x10 to open file for appending.
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); 
// write, create, truncate
// In a c file operation, we have no need to set file mode with or operation,
// directly using "r" or "w" usually.
//data = "foxtidy log\n";
//foStream.write(data, data.length);
//foStream.flush();

function DataListener(outstream, instream) {
	this.outstream = outstream;
	this.instream = instream;
	this.logString = "";
}
DataListener.prototype = {
	log : function(msg) {
		this.logString += Date.now() + "\t" + msg;
	},
	
	logFlush : function() {
		foStream.write(this.logString, this.logString.length);
		foStream.flush();
		this.logString = "";
	},
	
	onStartRequest : function (request, context) {},
	onStopRequest : function (request, context, status) {
	    this.instream.close();
	    this.outstream.close();
	},
	
	onDataAvailable : function (request, context, inputStream, offset, count) {
	    this.log("(start)\t");
	    dump("data available\n");
	    
		found_get = false;
		found_post = false;
	    data = this.instream.read(count);
	    this.log("(data read)\t");
	
	    if (data.match(/GET/)) {
	        found_get = true;
	    } else if (data.match(/POST/) && data.match(/[\n\r]{2}/)) {
	        found_post = true;
	    }
	
	    var headers = "HTTP/1.0 200 OK\nContent-type: text/html\n\n";
	    var body_header = "<html>"+
	        "<head><title>fftidy</title></head>" +
	        "<body><h1>fftidy</h1>";
	    var body_footer = "</div></body></html>\n";
	    
	    if (!found_get && !found_post) {
	        headers = "HTTP/1.0 405 Method Not Allowed\nContent-type: text/html\n\n";
	        var response = headers + body_header + "<h2>fftidy supports only GET or POST methods</h2>" + body_footer;
	        this.outstream.write(response, response.length);
	        
	        this.instream.close();
	        this.outstream.close();
	        return;
	    }
	    
	    if (found_get) {
	        var params = parseGETParams(data);
	    } else {
	        var params = parsePOSTParams(data);
	        var dataString = params['payload'];
	
	        // POST method requests must wrap the encoded text in a MIME stream
	        var stringStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
	        		.createInstance(Components.interfaces.nsIStringInputStream);
	        if ("data" in stringStream) { // Gecko 1.9 or newer
	            stringStream.data = dataString;
	        } else {// 1.8 or older
	            stringStream.setData(dataString, dataString.length);
	        }
	
	        var postData = Components.classes["@mozilla.org/network/mime-input-stream;1"]
	        		.createInstance(Components.interfaces.nsIMIMEInputStream);
	        postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
	        postData.addContentLength = true;
	        postData.setData(stringStream);
	        // postData is ready to be used as aPostData argument
	    }
	    dump("parsed headers\n");
		this.log("(" + params.url + ")\t");
	    
	    if (!params.url) {
	        var webpage = headers + body_header + "<h2>Malformed request(no url).</h2>" + body_footer;
	        this.outstream.write(webpage, webpage.length);
	        this.instream.close();
	        this.outstream.close();
	        return;
	    } 
	
	    if (!params.url.match(/^https?\:\/\/.*$/)) {
	        var headers = "HTTP/1.0 Bad Request\nContent-type: text/html\n\n";
	        var response = headers + body_header + getForm() + "<h2>" + url + " is not a valid HTTP URL</h2>" + body_footer;
	        this.outstream.write(response, response.length);
	        this.instream.close();
	        this.outstream.close();
	        return;
	    }
	    
	    url = params.url;
	    dump("Request: " + url + "\n");
	    
	    var m;
	    if (m = url.match(/\.(jpg|gif|png|ico)$/i)) {
			var contentType = "image/" + m[1];
			if (m[1].match(/ico/i)) {
				contentType = "image/x-icon";
			}
			
			dump("responding image... "+ contentType +"\n");
			
            var response = 
            	"HTTP/1.0 200 OK\nContent-type: " + contentType +"\n\n";
            this.outstream.write(response, response.length);
            
            //似乎没有从cache中获得该图片，将其送至其他stream的api
            //但有这么两种api
            //1. 把图片从cache保存至磁盘，再从磁盘传送至其他 stream
            //2. 重新下载图片，并传送至其他stream
            //暂时使用后者
            try {
			var ioserv = Components.classes["@mozilla.org/network/io-service;1"] 
				.getService(Components.interfaces.nsIIOService); 
			var channel = ioserv.newChannel(url, 0, null); 
			var istream = channel.open(); 
			
			if (channel instanceof Components.interfaces.nsIHttpChannel 
					&& channel.responseStatus != 200) { 
				dump("responding fails..\n");
	            this.instream.close();
	            this.outstream.close();
	            return;
			}

			// istream is a nsIInputStream and ostream is a nsIOutputStream  
			  
			// the output stream needs to be buffered to work.  
			var bostream = Components.classes["@mozilla.org/network/buffered-output-stream;1"]  
			               .createInstance(Components.interfaces.nsIBufferedOutputStream);  
			bostream.init(this.outstream, 0x8000);  
			
			// make a stream pump and a stream listener to read from the input stream for us  
			var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]  
			           .createInstance(Components.interfaces.nsIInputStreamPump);  
			pump.init(istream, -1, -1, 0, 0, true);  
			
			var is = this.instream;
			/* we need our own observer to know when to close the file */  
			var observer = {  
				onStartRequest: function(aRequest, aContext) {
					dump("observer: start\n");
				},  
				onStopRequest: function(aRequest, aContext, aStatusCode) {  
					dump("image respond end\n");
					bostream.close();
					is.close();
				}  
			};  
			
			// make a simple stream listener to do the writing to output stream for us  
			var listener = Components.classes["@mozilla.org/network/simple-stream-listener;1"]  
			               .createInstance(Components.interfaces.nsISimpleStreamListener);  
			listener.init(bostream, observer);  
			  
			// start the copying  
			pump.asyncRead(listener, null);  

			//this.instream.close();
            } catch (e) {
            	dump(e + "\n");
            }
			return;
	    }
	    
	    browserId = "foxtidy-browser" + (Math.random()+"").substring(2)
	    browser = document.getElementById(browserId);
	    if (!browser) {
			// create browser
			browser = document.createElement("browser");
			browser.setAttribute("id", browserId);
			browser.setAttribute("name", browserId);
			browser.setAttribute("type", "content");
			browser.style.height = "0px";
			document.documentElement.appendChild(browser);
			// set restrictions as needed
			browser.webNavigation.allowAuth = true;
			browser.webNavigation.allowImages = false;
			browser.webNavigation.allowJavascript = true;
			browser.webNavigation.allowMetaRedirects = true;
			browser.webNavigation.allowPlugins = false;
			browser.webNavigation.allowSubframes = false;
	    	balance++;
		} 
		this.log("(browser created)\t");
		
		dump("@TEST browser element:" + document.getElementById(browserId) + "\n");
		dump("@TEST:" + (document.getElementById(browserId) == browser) + "\n");
	    var dataListener = this;
	    var d = document;
	    /* TODO:确认代码的正确性
	     * 我怀疑某些时候DOMContentLoaded事件永远不会发生。在这种时候，就会发生
	     * 请求永远得不到响应的情况*/
	    browser.addEventListener("DOMContentLoaded", function(event) {
	    	try {
			dataListener.log("(DOMContentLoaded)\t");
		    this.removeEventListener("DOMContentLoaded", arguments.callee, false);
		    this.webNavigation.stop(nsIWebNavigation.STOP_ALL);
		    
		    dump("DOM content loaded...\n");
		    dataListener.respond(this.contentDocument);
		    //this, browser, 和document.getElementById(browserId)是同一个东西
		    //但是执行了下一句之后document.getElementById(browserId)将变成null
		    document.documentElement.removeChild(this);
		    balance--;
		    dump("@balance=" + balance + " "+ browserId +"\n");
			dataListener.log("(END)\n");
			dataListener.logFlush();
	    	} catch (e) {
	    		dump(e + "\n");
	    	}
	    }, true);

		dump("loading\n");
	    if (found_post){
	        browser.webNavigation.loadURI(url, 0, null, postData, null); 
	    } else {
	        browser.loadURI(url, null, null);
	    }
		this.log("(event&uri ok)\t");
	},
	
    respond : function(doc) {
        try {
            dump("starting response\n");
            
            var contentType = doc.contentType;
			var charset = doc.characterSet;
            
			dump("contentType: " + contentType + "\n");
			if (contentType == "text/html" || contentType == "application/xhtml+xml") {
				//我无法按照dom给出的charset发送html。因为mozilla似乎只支持utf-8，别的编码统统不支持。
				charset = "utf-8";
	            var response = "HTTP/1.0 200 OK\nContent-type: text/html;charset="+ charset +"\n\n";
	            this.outstream.write(response, response.length);
	            
				domString = DOMtoString(doc);
				
				var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
				                   .createInstance(Components.interfaces.nsIConverterOutputStream);
				os.init(this.outstream, charset, 0, "?".charCodeAt(0));
				os.writeString("<!--" + this.logString + "-->\n");
				os.writeString("<!--Generated by FoxTidy 0.1-->\n" + domString);
			} else if (contentType.match(/(text\/)|(javascript)|(css)/)) {
				charset = "utf-8";
	            var response = 
	            	"HTTP/1.0 200 OK\nContent-type: " + contentType + ";charset="+ charset +"\n\n";
	            this.outstream.write(response, response.length);
	            
				textString = doc.getElementsByTagName("pre")[0].innerHTML;
				
				var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
				                   .createInstance(Components.interfaces.nsIConverterOutputStream);
				os.init(this.outstream, charset, 0, "?".charCodeAt(0));
				os.writeString(textString);
			} else {
				dump("ignored: " + doc.URL + "\n");
			}
			
			dump("respond end\n");
            this.instream.close();
            this.outstream.close();
			this.log("(respond end)\t");
        } catch (e) {
            dump("Error " + e + "\n");
            dump(doc.URL + "\n");
        }
    }
}

function SocketListener() {}
SocketListener.prototype = {

    onStopListening: function(serv, status) {},

    onSocketAccepted: function(serv, transport) {
        try {
        	// make sure stream reading is blocking (see http://simile.mit.edu/issues/browse/CROWBAR-1)
            var outstream = transport.openOutputStream(1,0,0);
            var stream = transport.openInputStream(0,0,0);
            var instream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
            instream.init(stream);
        } catch (e) {
            dump("Error " + e);
        }

        var dataListener = new DataListener(outstream, instream);

        var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].createInstance(Components.interfaces.nsIInputStreamPump);
        pump.init(stream, -1, -1, 0, 0, false);
        dump("init\n");
        pump.asyncRead(dataListener, null);
    }
};

//将str打印至“错误控制台”
function jsdump(str) {
    Components.classes['@mozilla.org/consoleservice;1']
    	.getService(Components.interfaces.nsIConsoleService).logStringMessage(str);
}

function parseGETParams(data) {
    var params = {};
    var url = data.split(/[\n\r]/)[0].split(" ")[1];
    params['url'] = url;
    return params;
}

function parsePOSTParams(data) {
    var params = {};
    var splits = data.split(/[\n\r]{2}/);
    params['payload'] = splits[splits.length - 1];
    params['url'] = data.split(/[\n\r]/)[0].split(" ")[1];
    return params;
}

var globalStrings;
var port = 10000;           
var serverSocket;

function onLoad() {
	// initialization code
	globalStrings = document.getElementById("foxtidy-strings");
	
	//打开端口
    var socketListener = new SocketListener();
    serverSocket = Components.classes["@mozilla.org/network/server-socket;1"]
    		.createInstance(Components.interfaces.nsIServerSocket);
    serverSocket.init(port, false, -1);
    serverSocket.asyncListen(socketListener);
}

function onUnload() {
    serverSocket.close();
}

function aboutFoxtidy(event) {
	alert(globalStrings.getString("about.message"));
}

window.addEventListener("load", onLoad, false);
window.addEventListener("unload", onUnload, false);
